home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 2000 April: Mac OS SDK / Dev.CD Apr 00 SDK1.toast / Development Kits / Mac OS / AppleShare IP 6.3 SDK / ASIP Server Controls⁄Events / Sample Code / ServerControlSamples.c next >
Encoding:
C/C++ Source or Header  |  1999-11-01  |  16.5 KB  |  626 lines  |  [TEXT/CWIE]

  1.  
  2. #include "AppleShareFileServerControl.h"
  3.  
  4. #include <Errors.h>
  5. #include <LowMem.h>
  6. #include <Processes.h>
  7. #include <Sound.h>
  8.  
  9. #include <stdio.h>
  10. #include <stdlib.h>
  11. #include <time.h>
  12.  
  13. // ---------------------------------------------------------------------------------------
  14. // (Summary sample for "Starting and Stopping the File Server")
  15.  
  16. OSErr
  17. StartStopServer (Boolean startIt, SInt16 howLong);
  18. OSErr
  19. GetServerState (UInt16* state);
  20. OSErr
  21. ShutDownServer (SInt16 howLong);
  22. OSErr
  23. CancelShutDown (void);
  24. OSErr
  25. StartServer (void);
  26.  
  27. // Sample 1a:
  28. // StartStopServer shows you how to start or stop the AppleShare File Server based on 
  29. // what state it's currently in...
  30.  
  31. enum {
  32.     kRunningNormally,
  33.     kRunningButShuttingDown,
  34.     kNotRunning
  35. };
  36.  
  37. OSErr
  38. StartStopServer (Boolean startIt, SInt16 howLong) {
  39.  
  40.     OSErr         err = noErr;
  41.     UInt16         serverState;
  42.  
  43.     // In order to figure out what to send the server, we need to know what state it's in,
  44.     // and then make the appropriate judgement...
  45.     
  46.     err = GetServerState (&serverState);
  47.     if (err == noErr) {
  48.         if (startIt) {
  49.             if (serverState == kRunningNormally) {
  50.                 // We're already running--do nothing...
  51.             } else if (serverState == kRunningButShuttingDown) {
  52.                 err = CancelShutDown ();
  53.             } else {
  54.                 err = StartServer ();
  55.             } // if
  56.         } else {
  57.             if (serverState != kRunningNormally) {
  58.                 // We're either not running or will soon be not running--do nothing...
  59.             } else {
  60.                 err = ShutDownServer (howLong);
  61.             } // if
  62.         } // if
  63.     } // if
  64.     
  65.     return err;
  66.  
  67. } // StartStopServer
  68.  
  69. // Sample 1b:
  70. // GetServerState determines whether the server is running or not...
  71.  
  72. OSErr
  73. GetServerState (UInt16* state) {
  74.  
  75.     OSErr                 err = noErr;
  76.     SCParamBlockRec        serverControl;
  77.     PollServerParamPtr    pollParam = &serverControl.pollServerParam;
  78.     
  79.     pollParam->scCode = kSCPollServer;
  80.     pollParam->scSecondsLeft = 0;    // For MFS compatibility...
  81.     err = ServerDispatchSync (&serverControl);
  82.     if (pollParam->scServerState == kSCPollRunning) {
  83.         if (pollParam->scDisconnectState == kSCNotDisconnecting) {
  84.             *state = kRunningNormally;
  85.         } else {
  86.             *state = kRunningButShuttingDown;
  87.         } // if
  88.     } else if (pollParam->scServerState == kSCPollStartingUp) {
  89.         *state = kRunningNormally;    // will soon be up...
  90.     } else {
  91.         *state = kNotRunning;
  92.     } // if
  93.  
  94.     return err;
  95.  
  96. } // GetServerState
  97.  
  98. // Sample 1c:
  99. // StartServer starts the server...
  100.  
  101. OSErr
  102. StartServer (void) {
  103.  
  104.     OSErr                 err = noErr;
  105.     SCParamBlockRec        serverControl;
  106.     StartParamPtr        startParam = &serverControl.startParam;
  107.     
  108.     startParam->scCode = kSCStartServer;
  109.     startParam->scStartSelect = kSCCurrentlyInstalled;
  110.     startParam->scEventSelect = kSCUseFinderExtension;
  111.     err = ServerDispatchSync (&serverControl);
  112.  
  113.     return err;
  114.  
  115. } // StartServer
  116.  
  117. // Sample 1d:
  118. // ShutDownServer causes the server to shutdown after a specified time...
  119.  
  120. OSErr
  121. ShutDownServer (SInt16 howLong) {
  122.  
  123.     OSErr                 err = noErr;
  124.     SCParamBlockRec        serverControl;
  125.     DisconnectParamPtr    shutDownParam = &serverControl.disconnectParam;
  126.     
  127.     shutDownParam->scCode = kSCShutDown;
  128.     shutDownParam->scNumMinutes = howLong;
  129.     shutDownParam->scFlags = 0;
  130.     shutDownParam->scMessagePtr = "\pServer is Shutting Down!";
  131.  
  132.     err = ServerDispatchSync (&serverControl);
  133.  
  134.     return err;
  135.  
  136. } // ShutDownServer
  137.  
  138. // Sample 1e:
  139. // CancelShutDown aborts the server's shutdown sequence...
  140.  
  141. OSErr
  142. CancelShutDown (void) {
  143.  
  144.     OSErr                 err = noErr;
  145.     SCParamBlockRec        serverControl;
  146.     DisconnectParamPtr    disconnectParam = &serverControl.disconnectParam;
  147.     
  148.     disconnectParam->scCode = kSCCancelShutDown;
  149.  
  150.     err = ServerDispatchSync (&serverControl);
  151.  
  152.     return err;
  153.  
  154. } // CancelShutDown
  155.  
  156.  
  157. // ---------------------------------------------------------------------------------------
  158.  
  159. // (Summary sample for "SCGetExpFldr")
  160.  
  161. OSErr
  162. GetSharedVolumeInfo (SInt16 vRefNum[], SInt32 dirID[], SInt16 logins[], UInt16 arraySize);
  163. OSErr
  164. GetMinMaxIndexBounds (SInt16* minIndex, SInt16* maxIndex);
  165.  
  166. // Sample 2a:
  167. // GetSharedVolumeInfo returns information about mounted volumes...
  168.  
  169. OSErr
  170. GetSharedVolumeInfo (SInt16 vRefNum[], SInt32 dirID[], SInt16 logins[], UInt16 arraySize) {
  171.  
  172.     OSErr                 err = noErr;
  173.     UInt16                arrayUsed = 0;
  174.     SInt16                curIndex, minIndex, maxIndex;
  175.     SCParamBlockRec        serverControl;
  176.     StandardParamPtr    standardParam = &serverControl.standardParam;
  177.     
  178.     // Before we begin, we need to determine what the minimum and maximum
  179.     // index values for SCGetExpFldr are...
  180.     
  181.     err = GetMinMaxIndexBounds (&minIndex, &maxIndex);
  182.     if (err == noErr) {
  183.         curIndex = minIndex;
  184.         standardParam->scCode = kSCGetExpFldr;
  185.         standardParam->scNamePtr = NULL;    // We'll ignore the names...
  186.         while ((arrayUsed < arraySize) && (curIndex <= maxIndex)) {
  187.             err = ServerDispatchSync (&serverControl);
  188.             if (err == noErr) {
  189.                 vRefNum[arrayUsed] = standardParam->scVRefNum;
  190.                 dirID[arrayUsed] = standardParam->scDirID;
  191.                 logins[arrayUsed] = standardParam->scLogins;
  192.                 arrayUsed += 1;
  193.             } // if
  194.             curIndex += 1;
  195.             if (err == fnfErr) {
  196.                 err = noErr;    // Just means the position was empty
  197.             } // if
  198.             if (err != noErr) {
  199.                 break;
  200.             } // if
  201.         } // while
  202.     } // if
  203.  
  204.     return err;
  205.  
  206. } // GetSharedVolumeInfo
  207.  
  208. // Sample 2b:
  209. // GetMinMaxIndexBounds returns the range of indicies that will be valid...
  210.  
  211. OSErr
  212. GetMinMaxIndexBounds (SInt16* minIndex, SInt16* maxIndex) {
  213.  
  214.     OSErr                 err = noErr;
  215.     SCParamBlockRec        serverControl;
  216.     SetupInfoRec        setupInfo;
  217.     SetupParamPtr        setupParam = &serverControl.setupParam;
  218.     
  219.     setupParam->scCode = kSCGetSetupInfo;
  220.     setupParam->scSetupPtr = &setupInfo;
  221.  
  222.     err = ServerDispatchSync (&serverControl);
  223.     *minIndex = -setupParam->scMaxVolumes;    // Volumes are always negative...
  224.     *maxIndex = setupParam->scMaxExpFolders;
  225.     
  226.     return err;
  227.  
  228. } // GetMinMaxIndexBounds
  229.  
  230. // ---------------------------------------------------------------------------------------
  231. // (Summary Sample for "Sending Messages to Users")
  232.  
  233. OSErr
  234. SendGreetingToAll (void);
  235. OSErr
  236. GetNumberOfUsers (SInt32* numUsers);
  237. OSErr
  238. GetUser (SInt32 index, StringPtr name, SInt32* userID);
  239. OSErr
  240. SendUserMessage (SInt32 userID, StringPtr name);
  241.  
  242. // Sample 3a:
  243. // SendGreetingToAll sends the specified text message to every user...
  244. OSErr
  245. SendGreetingToAll (void) {
  246.  
  247.     OSErr                 err = noErr;
  248.     SInt32                userIndex, numUsers;
  249.     SInt32                userID;
  250.     Str255                userName;
  251.     
  252.     // We could send a message to all users at once, but we'll do it one at a time so
  253.     // that we can customize the message a little bit!
  254.     
  255.     err = GetNumberOfUsers (&numUsers);
  256.     if (err == noErr) {
  257.         userIndex = 0;
  258.         while (userIndex <= numUsers) {
  259.             err = GetUser (userIndex, userName, &userID);
  260.             if (err == noErr) {
  261.                 err = SendUserMessage (userID, userName);
  262.             } else if (err == fnfErr) {
  263.                 err = noErr;    // User does not exist at this session ID...
  264.             } // if
  265.             if (err != noErr) {
  266.                 break;
  267.             } // if
  268.             userIndex += 1;
  269.         } // while
  270.     } // if
  271.  
  272.     return err;
  273.  
  274. } // SendGreetingToAll
  275.  
  276. // Sample 3b:
  277. // GetNumberOfUsers returns the number of users on the system...
  278. OSErr
  279. GetNumberOfUsers (SInt32* numUsers) {
  280.  
  281.     OSErr                 err = noErr;
  282.     SCParamBlockRec        serverControl;
  283.     SetupInfoRec        setupInfo;
  284.     SetupParamPtr        setupParam = &serverControl.setupParam;
  285.     
  286.     setupParam->scCode = kSCGetSetupInfo;
  287.     setupParam->scSetupPtr = &setupInfo;
  288.  
  289.     err = ServerDispatchSync (&serverControl);
  290.     *numUsers = setupParam->scCurMaxSessions;
  291.     
  292.     return err;
  293.  
  294. } // GetNumberOfUsers
  295.  
  296. // Sample 3c:
  297. // GetUser takes an index, and returns a user name and ID...
  298. OSErr
  299. GetUser (SInt32 index, StringPtr name, SInt32* userID){
  300.  
  301.     OSErr                 err = noErr;
  302.     SCParamBlockRec        serverControl;
  303.     UserInfoParamPtr    userInfoParam = &serverControl.userInfoParam;
  304.     
  305.     userInfoParam->scCode = kSCGetUserNameRec;
  306.     userInfoParam->scNamePtr = name;
  307.     userInfoParam->scPosition = index;
  308.  
  309.     err = ServerDispatchSync (&serverControl);
  310.     *userID = userInfoParam->scUNRecID;
  311.     
  312.     return err;
  313.  
  314. } // GetUser
  315.  
  316. // Sample 3d:
  317. // SendUser message send the user a personalized message...
  318. OSErr
  319. SendUserMessage (SInt32 userID, StringPtr name) {
  320.  
  321.     OSErr                 err = noErr;
  322.     SCParamBlockRec        serverControl;
  323.     DisconnectParamPtr    messageParam = &serverControl.disconnectParam;
  324.     Str255                message = "\pHello ";
  325.     
  326.     messageParam->scCode = kSCSendMessage;
  327.     messageParam->scDiscArrayPtr = &userID;        // an array of 1
  328.     messageParam->scArrayCount = 1;
  329.     messageParam->scFlags = 0;
  330.     BlockMoveData (name, &message[StrLength(message) + 1], StrLength (name));
  331.     messageParam->scMessagePtr = message;
  332.  
  333.     err = ServerDispatchSync (&serverControl);
  334.  
  335.     return err;
  336.  
  337. } // SendUserMessage
  338.  
  339. // ---------------------------------------------------------------------------------------
  340. // (Summary Sample for "Using Server Event Handlers")
  341.  
  342. OSErr
  343. InstallOrRemoveEventHandler (ServerEventQEntryPtr seqEntry, Boolean install);
  344.  
  345. // Sample 4:
  346. // InstallRemoveEventHandler installs or removes an event handler queue entry from the
  347. // server...
  348.  
  349. OSErr
  350. InstallOrRemoveEventHandler (ServerEventQEntryPtr seqEntry, Boolean install) {
  351.  
  352.     OSErr                 err = noErr;
  353.     SCParamBlockRec        serverControl;
  354.     ServerEventParamPtr    serverEventParam = &serverControl.serverEventParam;
  355.  
  356.     if (install) {
  357.         serverEventParam->scCode = kSCInstallServerEventProc;
  358.     } else {
  359.         serverEventParam->scCode = kSCRemoveServerEventProc;
  360.     } // if
  361.     serverEventParam->scSEQEntryPtr = seqEntry;
  362.  
  363.     err = ServerDispatchSync (&serverControl);
  364.  
  365.     return err;
  366.  
  367. } // InstallOrRemoveEventHandler
  368.  
  369. // ---------------------------------------------------------------------------------------
  370. // (Summary Sample for "Sample Server Event Handler Code")
  371.  
  372. // Sample 5a:
  373. // We will queue events and process them in the main event loop.  We need to add some
  374. // structure to the standard data types...
  375.  
  376. // For convenience, add some fields on the front of a ServerEventRecord so that we can
  377. // use OS Queue manipulation routines on them...
  378. typedef struct {
  379.     QElemPtr                    qLink;    // Make OS queue-compatible...
  380.     SInt16                        qType;
  381.     ExtendedServerEventRecord    eventRec;
  382. } OurServerEventRecord;
  383.  
  384. // Similarly, we can add some fields after the ServerEventQEntry so that we have
  385. // access to them in our event handler...
  386. typedef struct {
  387.     ServerEventQEntry        queueEntry;    // actual queue entry...
  388.     QHdr                    freeQ;        // list of free OurServerEventRecord
  389.     QHdr                    usedQ;        // list of used OurServerEventRecord
  390. } OurServerEventQEntry;
  391.  
  392. // (Prototypes for completeness only)...
  393.  
  394. OSErr
  395. InitServerEventQueueData (OurServerEventQEntry* eventQueueEntry, OurServerEventRecord toQueue[],
  396.                 UInt32 numEventRecords);
  397. pascal void
  398. ServerEventHandler (OurServerEventQEntry* mainEntry, ExtendedServerEventRecord* event);
  399. void
  400. SetEventFlag (OurServerEventQEntry* mainEntry, UInt32 whichEvent, Boolean onOff);
  401. void
  402. SetControlFlag (OurServerEventQEntry* mainEntry, UInt32 whichEvent, Boolean onOff);
  403. void
  404. SetAFPFlag (OurServerEventQEntry* mainEntry, UInt32 whichEvent, Boolean inDo, 
  405.                 Boolean inReply, Boolean onOff);
  406. void
  407. ProcessQueuedEvents (OurServerEventQEntry* mainEntry);
  408.                 
  409. // Sample 5b:
  410. // InitServerEventQueue creates a custom queue entry containing everything we need to
  411. // go ahead and start receiving events...
  412.  
  413. OSErr
  414. InitServerEventQueueData (OurServerEventQEntry* eventQueueEntry, OurServerEventRecord toQueue[],
  415.                 UInt32 numEventRecords) {
  416.  
  417.     OSErr                             err = noErr;
  418.     QHdr                            emptyQueueInit = { 0, NULL, NULL };
  419.     static ServerEventHandlerUPP    ourCallBack = NULL;
  420.     
  421.     // Create the callback...
  422.     
  423.     if (ourCallBack == NULL) {
  424.         ourCallBack = NewServerEventHandlerProc (ServerEventHandler);
  425.     } // if
  426.     eventQueueEntry->queueEntry.callBack = ourCallBack;
  427.  
  428.     // Initially, clear all flags so we won't see any events...
  429.     
  430.     eventQueueEntry->queueEntry.serverEventMask = 0;
  431.     eventQueueEntry->queueEntry.afpCommandMask[0] = 0;
  432.     eventQueueEntry->queueEntry.afpCommandMask[1] = 0;
  433.     eventQueueEntry->queueEntry.serverControlMask = 0;
  434.  
  435.     // Caller gave us a block of OurServerEventRecords, which we should now push on the
  436.     // free queue...
  437.  
  438.     eventQueueEntry->freeQ = emptyQueueInit;
  439.     eventQueueEntry->usedQ = emptyQueueInit;
  440.     
  441.     while (numEventRecords > 0) {
  442.         numEventRecords -= 1;
  443.         Enqueue ((QElemPtr) &toQueue[numEventRecords], &eventQueueEntry->freeQ);
  444.     } // while
  445.  
  446.     return err;
  447.  
  448. } // InitServerEventQueueData
  449.  
  450. // Sample 5c:
  451. // ServerEventHandler receives events and puts them on a queue for the application to
  452. // process later...
  453. pascal void
  454. ServerEventHandler (OurServerEventQEntry* mainEntry, ExtendedServerEventRecord* event) {
  455.  
  456.     OSErr                        err = noErr;
  457.     OurServerEventRecord*        newEntry;
  458.  
  459.     // If there is free space in the queue, get it; if there is not, purge the oldest
  460.     // item in the used queue (you may want to behave differently, such as purging
  461.     // items that are of less interest, etc.)
  462.  
  463.     newEntry = (OurServerEventRecord*) mainEntry->freeQ.qHead;
  464.     if (newEntry != NULL) {
  465.         err = Dequeue ((QElemPtr) newEntry, &mainEntry->freeQ);
  466.     } else {
  467.         newEntry = (OurServerEventRecord*) mainEntry->usedQ.qHead;
  468.         err = Dequeue ((QElemPtr) newEntry, &mainEntry->usedQ);
  469.     } // if
  470.  
  471.     // Now we have an entry; stuff the event record into it, and requeue it on the
  472.     // "used" side...
  473.  
  474.     if (err == noErr) {
  475.         newEntry->eventRec = *event;
  476.         Enqueue ((QElemPtr) newEntry, &mainEntry->usedQ);
  477.     } // if
  478.  
  479. } // ServerEventHandler
  480.  
  481. // Sample 5d;
  482. // SetEventFlag determines what server _events_ a handler will receive...
  483. void
  484. SetEventFlag (OurServerEventQEntry* mainEntry, UInt32 whichEvent, Boolean onOff) {
  485.  
  486.     UInt32        maskValue = 0x1 << whichEvent;
  487.  
  488.     if (onOff) {
  489.         mainEntry->queueEntry.serverEventMask |= maskValue;
  490.     } else {
  491.         mainEntry->queueEntry.serverEventMask &= ~maskValue;
  492.     } // if
  493.  
  494. } // SetEventFlag
  495.  
  496. // Sample 5e;
  497. // SetControlFlag determines what server _control_ calls a handler will receive...
  498. void
  499. SetControlFlag (OurServerEventQEntry* mainEntry, UInt32 whichEvent, Boolean onOff) {
  500.  
  501.     UInt32        maskValue = 0x1 << whichEvent;
  502.  
  503.     if (onOff) {
  504.         mainEntry->queueEntry.serverControlMask |= maskValue;
  505.     } else {
  506.         mainEntry->queueEntry.serverControlMask &= ~maskValue;
  507.     } // if
  508.  
  509. } // SetControlFlag
  510.  
  511. // Sample 5f;
  512. // SetAFPFlag determines what AFP calls a handler will receive (only 1->64 are 
  513. // interceptable)...
  514. void
  515. SetAFPFlag (OurServerEventQEntry* mainEntry, UInt32 whichEvent, Boolean inDo, 
  516.                 Boolean inReply, Boolean onOff) {
  517.  
  518.     UInt32        maskValue0 = 0;
  519.     UInt32        maskValue1 = 0;
  520.  
  521.     // Special case of AddIcon gets remapped to bit 0...
  522.     if (whichEvent == afpAddIcon) {
  523.         whichEvent = 0;
  524.     } // if
  525.     
  526.     if (whichEvent >= 32) {
  527.         maskValue0 = 1 << (whichEvent % 32);
  528.     } else {
  529.         maskValue1 = 1 << whichEvent;
  530.     } // if
  531.  
  532.     if (onOff) {
  533.         mainEntry->queueEntry.afpCommandMask[0] |= maskValue0;
  534.         mainEntry->queueEntry.afpCommandMask[1] |= maskValue1;
  535.     } else {
  536.         mainEntry->queueEntry.afpCommandMask[0] &= ~maskValue0;
  537.         mainEntry->queueEntry.afpCommandMask[1] &= ~maskValue1;
  538.     } // if
  539.  
  540.     // We need to set the appropriate Event flag(s) so this actually gets called...
  541.     
  542.     if (inDo) {
  543.         SetEventFlag (mainEntry, kSCStartAFPRequestEvt, onOff);
  544.     } // if
  545.     if (inReply) {
  546.         SetEventFlag (mainEntry, kSCSendAFPResponseEvt, onOff);
  547.     } // if
  548.  
  549. } // SetAFPFlag
  550.  
  551. // Sample 5g:
  552. // ProcessQueuedEvents goes through the events that have queued up, and does something
  553. // with them (in our case, simply beeps)...
  554. void
  555. ProcessQueuedEvents (OurServerEventQEntry* mainEntry) {
  556.  
  557.     OSErr                        err = noErr;
  558.     OurServerEventRecord*        nextEntry;
  559.  
  560.     nextEntry = (OurServerEventRecord*) mainEntry->usedQ.qHead;
  561.     if (nextEntry != NULL) {
  562.         err = Dequeue ((QElemPtr) nextEntry, &mainEntry->usedQ);
  563.         if (err == noErr) {
  564.             SysBeep (0);
  565.             Enqueue ((QElemPtr) nextEntry, &mainEntry->freeQ);
  566.         } // if
  567.     } // if
  568.  
  569. } // ProcessQueuedEvents
  570.  
  571. // ---------------------------------------------------------------------------------------
  572.  
  573. // Test code to see that it all works...
  574.  
  575. #define    kMaxNumSharePoints            10
  576. #define    kMaxNumQueueEvents            100
  577. #define    kNumberOfSecondsToIntercept    60
  578.  
  579. int main(void)
  580. {
  581.  
  582.     SInt16                        vRefNum[kMaxNumSharePoints];
  583.     SInt32                        dirID[kMaxNumSharePoints];
  584.     SInt16                        logins[kMaxNumSharePoints];
  585.     OurServerEventQEntry        ourEntry;
  586.     OurServerEventRecord        queuedEvents[kMaxNumQueueEvents];
  587.     UInt32                        start;
  588.  
  589.     printf ("Begin Test\n");
  590.  
  591.     // Sample 1 test...
  592.  
  593.     StartStopServer (true, 1);
  594.  
  595.     // Sample 2 test...
  596.  
  597.     GetSharedVolumeInfo (vRefNum, dirID, logins, kMaxNumSharePoints);
  598.     
  599.     // Sample 3 test...
  600.     
  601.     SendGreetingToAll ();
  602.     
  603.     // Sample 4, 5 test...
  604.  
  605.     InitServerEventQueueData (&ourEntry, queuedEvents, kMaxNumQueueEvents);
  606.     SetEventFlag (&ourEntry, kSCVolumePrepEvt, true);
  607.     SetControlFlag (&ourEntry, kSCDisconnect, true);
  608.     SetAFPFlag (&ourEntry, afpAddIcon, true, false, true);
  609.     SetAFPFlag (&ourEntry, afpAddAPPL, true, true, true);
  610.     SetAFPFlag (&ourEntry, afpDelete, true, true, true);
  611.     
  612.     InstallOrRemoveEventHandler (&ourEntry.queueEntry, true);
  613.  
  614.     start = LMGetTicks ();
  615.     while (start + kNumberOfSecondsToIntercept * 60 > LMGetTicks ()) {
  616.         if (LMGetTicks () % 60 == 0) printf ("Tick %d\n", LMGetTicks ());
  617.         ProcessQueuedEvents (&ourEntry);
  618.     } // while
  619.  
  620.  
  621.     InstallOrRemoveEventHandler (&ourEntry.queueEntry, false);
  622.  
  623.     return 0;
  624. }
  625.  
  626.